#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <gccore.h>
#include <ogcsys.h>
#include <string.h>
#ifdef WII
#include <wiiuse/wpad.h>
#endif
#include <ogc/pad.h>

#define DEFAULT_FIFO_SIZE	( 256*1024 )

static void *frameBuffer[2] = { NULL, NULL };
GXRModeObj *rmode;

#define BOARD_ROWS 6
#define BOARD_COLS 10

#ifdef WII
#define LIFE_TIME 20
#else
#define LIFE_TIME 3
#endif

#define EMPTY 0
#define BLOCK_SIMPLE 1
#define BLOCK_DOUBLE 2
#define BLOCK_TRIPLE 3

#define CELL_ALIVE 0
#define CELL_DEAD 1
#define CELL_WILL_DIE 2
#define CELL_WILL_BORN 3
#define CELL_SIZE 0.45f
#define CELL_HALFSIZE 0.45f/2


#define BLOCK_SIZE 0.45f
#define BLOCK_HALFSIZE 0.45f/2
int getPos(int *x, int *y){
	//out of bounds return false
	if( (*x < 0)
		| (*x >= BOARD_COLS )
		| (*y < 0)
		| (*y >= BOARD_ROWS)
		){
		return false ;
	}
	return (*y * BOARD_COLS) + *x;
}
bool isAlive(int board[], int x, int y){
	int cellPos = getPos(&x, &y);
	if( false == cellPos ) return false;
	 int *cell = &board[cellPos];
	return *cell == CELL_ALIVE || *cell == CELL_WILL_DIE;
}
float getColor( float finalColor, int frame, int finalFrame ){
	return (finalColor/finalFrame)*(frame+1);
}
void drawCell( int cell_state, int frame );
void textToLife( int *board, int initialX, int initialY, char *input ){
	int row = initialY;
	int col = initialX;
	int position, i = 0;
	// loop on the string
	while( input[i] != '\0' ){
		//if we've found a newline char, jump to the row bellow
		if( input[i] == '\t' ){
			row--;
			col = initialX;
		}else{
			// get actual cursor
			position = getPos(&row, &col);
			if( false != position ){
				// mark as live?
				if( input[i] != '.' ){
					board[position] = CELL_ALIVE;
				}
			}
		col++;
		}
		i++;
		//if( i > 255 ){ // some limits
		//	break;
		//}
	}
}

int doreload=0, dooff=0;
void reload(void) { doreload=1; }
void shutdown(void) { dooff=1; }
#ifdef WII
// NOTE: aren't those two needed for gamecube?
void _wiimotePowerPressed(s32 chan){
	//I cant find this on any lib :(
	//SYS_ResetSystem( SYS_POWEROFF, 0, 0 );
}
// related to wpad input document what this does exactly. just a event counter?
int evctr = 0;
void countevs(int chan, const WPADData *data) {
   evctr++;
}
#endif

//---------------------------------------------------------------------------------
int main( int argc, char **argv ){
//---------------------------------------------------------------------------------
	f32 yscale;

	u32 xfbHeight;
	Mtx view, perspective; // view and perspective matrices
	Mtx model, modelview;

	u32	fb = 0; 	// initial framebuffer index
	GXColor background = {0, 0, 0, 0xff};

	VIDEO_Init();
	//input
	//PAD_Init();
	//PADStatus pads[4];
#define INPUT_VIEW 0
#define INPUT_EDIT 1
#define INPUT_MENU 3
	int inputMode = INPUT_VIEW;
#ifdef WII
	WPAD_Init();
	WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR);
	//WPAD_SetVRes(0, rmode->fbWidth, rmode->xfbHeight);
	//I cant find this function anywhere :(
	//WPAD_SetPowerButtonCallback(_wiimotePowerPressed);
	SYS_SetResetCallback(reload);
	SYS_SetPowerCallback(shutdown);
	int WPAD_res;
	WPADData *WPAD_d;
	u32 type; //what's this about?
	gforce_t gforce;
	//OFF ir_t  ir;
#endif


	// video modes. still need to figure out all this.
	rmode = VIDEO_GetPreferredMode(NULL); VIDEO_Configure(rmode); 
	// allocate 2 framebuffers for double buffering
	frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	VIDEO_SetNextFramebuffer(frameBuffer[fb]); VIDEO_SetBlack(FALSE); VIDEO_Flush();
	if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();
	// setup the fifo and then init the flipper
	void *gp_fifo = NULL; gp_fifo = memalign(32,DEFAULT_FIFO_SIZE); memset(gp_fifo,0,DEFAULT_FIFO_SIZE); GX_Init(gp_fifo,DEFAULT_FIFO_SIZE);
	// clears the bg to color and clears the z buffer
	GX_SetCopyClear(background, 0x00ffffff);

	// other gx setup
	GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);
	yscale = GX_GetYScaleFactor( rmode->efbHeight, rmode->xfbHeight );
	xfbHeight = GX_SetDispCopyYScale(yscale);
	GX_SetScissor(0,0,rmode->fbWidth,rmode->efbHeight);

	// all this is used for the double(?) framebuffer
	GX_SetDispCopySrc(0,0,rmode->fbWidth,rmode->efbHeight); GX_SetDispCopyDst(rmode->fbWidth,xfbHeight); GX_SetCopyFilter(rmode->aa,rmode->sample_pattern,GX_TRUE,rmode->vfilter); GX_SetFieldMode(rmode->field_rendering,((rmode->viHeight==2*rmode->xfbHeight)?GX_ENABLE:GX_DISABLE));
	GX_SetCullMode(GX_CULL_NONE); GX_CopyDisp(frameBuffer[fb],GX_TRUE); GX_SetDispCopyGamma(GX_GM_1_0);

	// setup the vertex descriptor
	// tells the flipper to expect direct data
	GX_ClearVtxDesc();
	GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
 	GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);

	// setup the vertex attribute table
	// describes the data
	// args: vat location 0-7, type of data, data format, size, scale
	// so for ex. in the first call we are sending position data with
	// 3 values X,Y,Z of size F32. scale sets the number of fractional
	// bits for non float data.
	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
	GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGB8, 0);

	GX_SetNumChans(1); GX_SetNumTexGens(0);
	GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLOR0A0);
	GX_SetTevOp(GX_TEVSTAGE0, GX_PASSCLR);



	// setup our projection matrix
	// this creates a perspective matrix with a view angle of 90,
	// and aspect ratio based on the display resolution
	f32 w = rmode->viWidth; f32 h = rmode->viHeight;
	guPerspective(perspective, 45, (f32)w/h, 0.1F, 300.0F);
	GX_LoadProjectionMtx(perspective, GX_PERSPECTIVE);


	//-----------------------------INICIO-------------------------
	

	float roll_accel;
	float roll;
	// configure board
	int board[ BOARD_ROWS * BOARD_COLS ];
	int board_index = 0;
	for( board_index = 0; board_index < BOARD_ROWS*BOARD_COLS; board_index++){
		board[board_index] = EMPTY;
		// TODO: need to create a structure instead of bool.
		// it must hold the Fate of the the cell in the next interation.
		// or it can be tinyint and add a new CELL_WILL_DIE, CELL_WILL_BORN, CELL_WILL_REMAIN constants
	}

// draw initial board
char *glider = "\
	.x.\
	..x\
	xxx\0";

		// random
		for( board_index = 0; board_index < BOARD_ROWS*BOARD_COLS; board_index++){
			board[board_index] = rand()%2;
		}


	int frame = 1;
	int i, j; //TODO: better names for those. these are rols/cols for the array
	float x, y; // these are x,y for the board
	bool prepStep = false;
	// Timing Variables
	//clock_t time_now, movement_timer = 0;
	// we're not read yet. http://nehe.gamedev.net/data/articles/article.asp?article=17
   while(!doreload && !dooff) { /////////////////////////////////////////////////////////  MAIN LOOP ///
#if WII
		WPAD_ReadPending(WPAD_CHAN_ALL, countevs);
		WPAD_res = WPAD_Probe(0, &type);
		if( WPAD_res == WPAD_ERR_NONE ){
			WPAD_d = WPAD_Data(0);
			// [HOME]
			if( WPAD_d->btns_h & WPAD_BUTTON_HOME ) exit(0);
			// [DOWN]
			if( inputMode == INPUT_VIEW ){
				// ROLL/PITCH (both are an angle) 
				WPAD_GForce(0, &gforce);
				//roll
				if( roll_accel > 0 ) roll_accel -= 0.001; // deacelerates..
				if( roll_accel < 0 ) roll_accel += 0.001; // ..
				if( camera_z > 0 ){
					roll_accel += ((float)gforce.x) * 0.003;
				}else{
					roll_accel += ((float)gforce.x) * -0.003;
				}
			}else{
				//first, stop movement
				roll_accel = 0;
			}
		}
#endif
		//ROLL
		// limit degress of aceleration
		if( roll_accel >  1.0 ) roll_accel =  1.0;
		if( roll_accel < -1.0 ) roll_accel = -1.0;
		roll += roll_accel;

		frame++;
		// do this before drawing
		GX_SetViewport(0,0,rmode->fbWidth,rmode->efbHeight,0,1);

		// draw board
		for( i=0; i < BOARD_ROWS; i++ ){
			y = 0.5f+i - BOARD_ROWS*0.5; // have no clue why i need the +0.5 there
			for( j=0; j < BOARD_COLS; j++ ){
				x =0.5f+ j - BOARD_COLS*0.5;
				int cellPos = BOARD_COLS*i+j;
				int *cell = &board[cellPos];
				// do a next evolution step?
					// draw next generation on board
					if( *cell == CELL_WILL_DIE ){
						*cell = CELL_DEAD;
					}else if( *cell == CELL_WILL_BORN ){
						*cell = CELL_ALIVE;
					}
				//DRAW
					// glTranslatef()
					guMtxIdentity(model);
					//guMtxRotAxisDeg(model, &Yaxis, rtri);
					guMtxTransApply(model, model, x, y, 0.0f);
					guMtxConcat(view,model,modelview);
					// load the modelview matrix into matrix memory
					GX_LoadPosMtxImm(modelview, GX_PNMTX0);
					// /glTranslatef()
					drawCell( board[ BOARD_COLS*i + j ], frame%LIFE_TIME );
				// /DRAW
			}
		}
		//if( doStep && cellsAlive < 1 ){ exit(0); }

		// do this stuff after drawing
		GX_DrawDone();
		fb ^= 1;		// flip framebuffer
		GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
		GX_SetColorUpdate(GX_TRUE);
		GX_CopyDisp(frameBuffer[fb],GX_TRUE);
		VIDEO_SetNextFramebuffer(frameBuffer[fb]);
		VIDEO_Flush();
		//probably only needed in CRT?
		VIDEO_WaitVSync();
	}
	if(doreload) SYS_ResetSystem( SYS_RETURNTOMENU, 0, 0 );
	if(dooff) SYS_ResetSystem(SYS_POWEROFF,0,0); // seems to do nothing.
	return 0;
}
void drawCell( int cell_state, int frame ){
	float r = 0.0;
	float g = 0.0;
	float b = 0.0;
	float r1 = 0.0; // fancy higlighted corner
	float g1 = 0.0; // fancy higlighted corner
	float b1 = 0.0; // fancy higlighted corner
	if( cell_state == CELL_ALIVE ){
		g = 0.5;
		g1 = 0.4;
	}else if( cell_state == CELL_DEAD ){
		g = 0.1;
	}else if( cell_state == CELL_WILL_BORN ){
		g = getColor( 0.5, frame, LIFE_TIME );
		g1 = g+0.06;
	}else if( cell_state == CELL_WILL_DIE ){
		g = 0.5 - getColor( 0.5, frame, LIFE_TIME );
		g1 = g;
	}else{
		r = 1; r1 = 1;
		b = 0.1; b1 = 0.1;
	}
	GX_Begin(GX_QUADS, GX_VTXFMT0, 24);			// Draw A Quad
		// observer front
		GX_Position3f32(-CELL_SIZE, CELL_SIZE, 0.0f);	// Top Left
		GX_Color3f32(r1, g1, b1);
		GX_Position3f32( CELL_SIZE, CELL_SIZE, 0.0f);		// Top Right
		GX_Color3f32(r, g, b);
		GX_Position3f32( CELL_SIZE,-CELL_SIZE, 0.0f);	// Bottom Right
		GX_Color3f32(r, g, b);
		GX_Position3f32(-CELL_SIZE,-CELL_SIZE, 0.0f);	// Bottom Left
		GX_Color3f32(r, g, b);
		//top
		GX_Position3f32(-CELL_SIZE, CELL_SIZE, CELL_SIZE);	// Top Left
		GX_Color3f32(r1, g1, b1);
		GX_Position3f32( CELL_SIZE, CELL_SIZE, CELL_SIZE);		// Top Right
		GX_Color3f32(r, g, b);
		GX_Position3f32( CELL_SIZE, CELL_SIZE, 0.0f);	// Bottom Right
		GX_Color3f32(r, g, b);
		GX_Position3f32(-CELL_SIZE, CELL_SIZE, 0.0f);	// Bottom Left
		GX_Color3f32(r, g, b);
		//bottom
		GX_Position3f32(-CELL_SIZE, -CELL_SIZE, 0.0f);	// Top Left
		GX_Color3f32(r1, g1, b1);
		GX_Position3f32( CELL_SIZE, -CELL_SIZE, 0.0f);		// Top Right
		GX_Color3f32(r, g, b);
		GX_Position3f32( CELL_SIZE, -CELL_SIZE, CELL_SIZE);	// Bottom Right
		GX_Color3f32(r, g, b);
		GX_Position3f32(-CELL_SIZE, -CELL_SIZE, CELL_SIZE);	// Bottom Left
		GX_Color3f32(r, g, b);
		// back invertido
		GX_Position3f32(-CELL_SIZE, CELL_SIZE, 0.0f);	// Top Left
		GX_Color3f32(r1, g1, b1);
		GX_Position3f32( CELL_SIZE, CELL_SIZE, 0.0f);		// Top Right
		GX_Color3f32(r, g, b);
		GX_Position3f32( CELL_SIZE,-CELL_SIZE, 0.0f);	// Bottom Right
		GX_Color3f32(r, g, b);
		GX_Position3f32(-CELL_SIZE,-CELL_SIZE, 0.0f);	// Bottom Left
		GX_Color3f32(r, g, b);
		// observer right
		GX_Position3f32( CELL_SIZE, CELL_SIZE, 0.0f);	// Top Left
		GX_Color3f32(r1, g1, b1);
		GX_Position3f32( CELL_SIZE, CELL_SIZE, CELL_SIZE);		// Top Right
		GX_Color3f32(r, g, b);
		GX_Position3f32( CELL_SIZE,-CELL_SIZE, CELL_SIZE);	// Bottom Right
		GX_Color3f32(r, g, b);
		GX_Position3f32( CELL_SIZE,-CELL_SIZE, 0.0f);	// Bottom Left
		GX_Color3f32(r, g, b);
		// observer left
		GX_Position3f32(-CELL_SIZE, CELL_SIZE, CELL_SIZE);	// Top Left
		GX_Color3f32(r1, g1, b1);
		GX_Position3f32(-CELL_SIZE, CELL_SIZE, 0.0f);		// Top Right
		GX_Color3f32(r, g, b);
		GX_Position3f32(-CELL_SIZE,-CELL_SIZE, 0.0f);	// Bottom Right
		GX_Color3f32(r, g, b);
		GX_Position3f32(-CELL_SIZE,-CELL_SIZE, CELL_SIZE);	// Bottom Left
		GX_Color3f32(r, g, b);
	GX_End();
}
